package com.sharing.recommendmodule.service;

import com.sharing.common.Result;
import com.sharing.domain.*;
import com.sharing.domain.dto.ProductionDto;
import com.sharing.domain.dto.UserDto;
import com.sharing.recommendmodule.mapper.RecommendMapper;
import com.sharing.service.*;
import com.sharing.util.IBloomFilterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author MingTian
 * @version 1.0
 * @description TODO
 * @date 2023/6/23 17:18
 */
@Service
public class RecommendService {
    @Autowired
    private RecommendMapper recommendMapper;

    @Autowired
    private IBloomFilterService iBloomFilterService;

    @Autowired
    private UserHobbyService userHobbyService;

    @Autowired
    private UserProductionService userProductionService;

    @Autowired
    private ProductionHobbyService productionHobbyService;

    @Autowired
    private HobbyService hobbyService;

    @Autowired
    private UserLikeService userLikeService;

    @Autowired
    private UserService userService;

    /**
     * 对指定用户推荐首页作品（可指定兴趣范围）的业务层方法
     * @param userId
     * @param page
     * @param size
     * @param hobbyIds 若该参数为空，代表依据用户默认兴趣进行推荐（后续可通过定时任务依据用户的浏览行为动态修改用户的兴趣）
     * @return
     */
    public Result<List<UserProduction>> recommendHomePageProduction(Integer userId, Integer page, Integer size, List<Integer> hobbyIds) {
        // 校验参数 —— userId
        if (userId == null) {
            return new Result<>(1, "userId 不能为空");
        }

        // 校验参数 —— page
        if (page == null || page <= 0) {
            return new Result<>(1, "page 参数必须为正整数");
        }

        // 校验参数 —— size
        if (size == null || size <= 0) {
            return new Result<>(1, "size 参数必须为正整数");
        }

        // 根据 hobbyIds 是否为空划分两种推荐机制
        if (hobbyIds == null) {
            // hobbyIds 代表用户选择系统根据用户的兴趣进行推荐
            // step1：查询该用户的一级兴趣
            List<Hobby> hobbiesOne = userHobbyService.selectHobbyByUserId(userId, 1, 1, 5).getData().getResult();
            List<String> hobbiesOneString = new ArrayList<>(hobbiesOne.size());
            for (int i = 0; i < hobbiesOne.size(); i++) {
                hobbiesOneString.set(i, hobbiesOne.get(i).getHobby());
            }

            // step2：查询该用户的二级兴趣
            List<Hobby> hobbiesSecond = userHobbyService.selectHobbyByUserId(userId, 2, 1, 5).getData().getResult();
            List<String> hobbiesSecondString = new ArrayList<>(hobbiesSecond.size());
            for (int i = 0; i < hobbiesSecond.size(); i++) {
                hobbiesSecondString.set(i, hobbiesSecond.get(i).getHobby());
            }

            // step3：根据一级兴趣查询作品
            List<ProductionDto> userProductionsOne = productionHobbyService.selectProductionDtoByHobby(hobbiesOneString, page, size).getData().getResult();

            // step4：根据二级兴趣查询作品
            List<ProductionDto> userProductionsSecond = productionHobbyService.selectProductionDtoByHobby(hobbiesSecondString, page, size).getData().getResult();

            // step5：组装结果集，通过布隆过滤器删除掉已经浏览过的作品
            List<UserProduction> userProductions = new ArrayList<>();
            for (int i = 0; i < userProductionsSecond.size(); i++) {
                if (iBloomFilterService.exist("userId" + userId + "_browse_record", String.valueOf(userProductionsSecond.get(i).getId()))) {
                    continue;
                }
                userProductions.add(userProductionsSecond.get(i));
            }

            for (int i = 0; i < userProductionsOne.size(); i++) {
                if (iBloomFilterService.exist("userId" + userId + "_browse_record", String.valueOf(userProductionsOne.get(i).getId()))) {
                    continue;
                }
                userProductions.add(userProductionsOne.get(i));
                if (userProductions.size() == size) {
                    break;
                }
            }

            // 返回结果
            return new Result<>(0, userProductions);
        } else {
            // 若用户指定推荐某些兴趣的作品
            List<UserProduction> userProductions = new ArrayList<>();

            List<String> hobby = new ArrayList<>();

            // 核心逻辑：根据用户勾选的兴趣先后顺序进行推荐，即越靠前的兴趣其作品的优先级越高
            // 只有当优先级高的作品因布隆过滤器过滤掉已经浏览的记录导致作品数量不满足参数指定的 size 时，才进行下一个兴趣的作品查询
            for (int i = 0; i < hobbyIds.size(); i++) {
                hobby.add(hobbyService.selectHobbyByHobbyId(hobbyIds.get(i)));
                List<ProductionDto> productionDtos = productionHobbyService.selectProductionDtoByHobby(hobby, page, size).getData().getResult();
                hobby.remove(0);

                for (int j = 0; j < productionDtos.size(); j++) {
                    if (iBloomFilterService.exist("userId" + userId + "_browse_record", String.valueOf(productionDtos.get(i).getId()))) {
                        continue;
                    }
                    userProductions.add(productionDtos.get(i));
                    if (userProductions.size() == size) {
                        break;
                    }
                }

                if (userProductions.size() == size) {
                    break;
                }
            }

            return new Result<>(0, userProductions);
        }
    }

    /**
     * 类似兴趣用户推荐
     * 系统将根据该用户的兴趣，推荐常常发布对应兴趣作品的用户
     * 注意：应筛选掉该用户已经关注的用户
     * @param userId
     * @param page
     * @param size
     * @return
     */
    public Result<List<User>> recommendUserByProduction(Integer userId, Integer page, Integer size) {
        // 校验参数 —— userId
        if (userId == null) {
            return new Result<>(1, "userId 不能为空");
        }

        // 校验参数 —— page
        if (page == null || page <= 0) {
            return new Result<>(1, "page 参数必须为正整数");
        }

        // 校验参数 —— size
        if (size == null || size <= 0) {
            return new Result<>(1, "size 参数必须为正整数");
        }

        // 查询该用户的兴趣
        // 一级兴趣
        List<Hobby> hobbiesOne = userHobbyService.selectHobbyByUserId(userId, 1, 1, 5).getData().getResult();
        List<String> hobbiesOneString = new ArrayList<>(hobbiesOne.size());
        for (int i = 0; i < hobbiesOne.size(); i++) {
            hobbiesOneString.set(i, hobbiesOne.get(i).getHobby());
        }

        // 二级兴趣
        List<Hobby> hobbiesSecond = userHobbyService.selectHobbyByUserId(userId, 2, 1, 5).getData().getResult();
        List<String> hobbiesSecondString = new ArrayList<>(hobbiesSecond.size());
        for (int i = 0; i < hobbiesSecond.size(); i++) {
            hobbiesSecondString.set(i, hobbiesSecond.get(i).getHobby());
        }

        // 获取指定兴趣推荐的用户信息
        List<UserDto> usersOne = userHobbyService.selectUsersByHobby(hobbiesOneString, page, size).getData().getResult();
        List<UserDto> usersSecond = userHobbyService.selectUsersByHobby(hobbiesSecondString, page, size).getData().getResult();

        // 获取该用户已经关注的用户
        List<User> userLikes = userLikeService.selectLikeUsersByUserId(userId, 1, Integer.MAX_VALUE).getData().getResult();

        // 将已关注的用户id放入set集合方便判断某一个用户是否已经被关注
        Set<Integer> userLikesId = new HashSet<>();
        for (int i = 0; i < userLikes.size(); i++) {
            userLikesId.add(userLikes.get(i).getId());
        }

        // 创建结果集合
        List<User> users = new ArrayList<>();
        for (int i = 0; i < usersSecond.size(); i++) {
            if (!userLikesId.contains(usersSecond.get(i).getId())) {
                users.add(usersSecond.get(i));
            }
        }

        for (int i = 0; i < usersOne.size(); i++) {
            if (!userLikesId.contains(usersOne.get(i).getId())) {
                users.add(usersOne.get(i));
            }
        }

        return new Result<>(0, users);
    }

    /**
     * 附近用户推荐的业务层方法
     * 对于查询附近的用户，有如下两种方案：
     *      方案一：在 sql 语句层面，添加经纬度的过滤条件
     *      方案二：使用 GeoHash 算法
     * 此处的业务层方法将采用方案一进行
     * @param userId
     * @param page
     * @param size
     * @return
     */
    public Result<List<User>> recommendUserByPosition(Integer userId, Integer page, Integer size) {
        // 校验参数 —— userId
        if (userId == null) {
            return new Result<>(1, "userId 不能为空");
        }

        // 校验参数 —— page
        if (page == null || page <= 0) {
            return new Result<>(1, "page 参数必须为正整数");
        }

        // 校验参数 —— size
        if (size == null || size <= 0) {
            return new Result<>(1, "size 参数必须为正整数");
        }

        User user = userService.selectUserById(userId).getData();
        List<User> users = userService.selectUserByPosition(user, page, size);

        return new Result<>(0, users);
    }
}
